// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/tools/flip_server/tcp_socket_util.h"

#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#include "base/files/file_util.h"
#include "base/logging.h"
#include "net/socket/tcp_socket.h"

namespace net {

namespace {

    // Used to ensure we delete the addrinfo structure alloc'd by getaddrinfo().
    class AddrinfoGuard {
    public:
        explicit AddrinfoGuard(struct addrinfo* addrinfo_ptr)
            : addrinfo_ptr_(addrinfo_ptr)
        {
        }

        ~AddrinfoGuard() { freeaddrinfo(addrinfo_ptr_); }

    private:
        struct addrinfo* addrinfo_ptr_;
    };

    // Summary:
    //   Closes a socket, with option to attempt it multiple times.
    //   Why do this? Well, if the system-call gets interrupted, close
    //   can fail with EINTR. In that case you should just retry.. Unfortunately,
    //   we can't be sure that errno is properly set since we're using a
    //   multithreaded approach in the filter proxy, so we should just retry.
    // Args:
    //   fd - the socket to close
    //   tries - the number of tries to close the socket.
    // Returns:
    //   true - if socket was closed
    //   false - if socket was NOT closed.
    // Side-effects:
    //   sets *fd to -1 if socket was closed.
    //
    bool CloseSocket(int* fd, int tries)
    {
        for (int i = 0; i < tries; ++i) {
            if (!close(*fd)) {
                *fd = -1;
                return true;
            }
        }
        return false;
    }

} // namespace

int CreateTCPServerSocket(const std::string& host,
    const std::string& port,
    bool is_numeric_host_address,
    int backlog,
    bool reuseaddr,
    bool reuseport,
    bool wait_for_iface,
    bool disable_nagle,
    int* listen_fd)
{
    // start out by assuming things will fail.
    *listen_fd = -1;

    const char* node = NULL;
    const char* service = NULL;

    if (!host.empty())
        node = host.c_str();
    if (!port.empty())
        service = port.c_str();

    struct addrinfo* results = 0;
    struct addrinfo hints;
    memset(&hints, 0, sizeof(hints));

    if (is_numeric_host_address) {
        hints.ai_flags = AI_NUMERICHOST; // iff you know the name is numeric.
    }
    hints.ai_flags |= AI_PASSIVE;

    hints.ai_family = PF_INET;
    hints.ai_socktype = SOCK_STREAM;

    int err = 0;
    if ((err = getaddrinfo(node, service, &hints, &results))) {
        // gai_strerror -is- threadsafe, so we get to use it here.
        LOG(ERROR) << "getaddrinfo "
                   << " for (" << host << ":" << port << ") " << gai_strerror(err)
                   << "\n";
        return -1;
    }
    // this will delete the addrinfo memory when we return from this function.
    AddrinfoGuard addrinfo_guard(results);

    int sock = socket(results->ai_family, results->ai_socktype, results->ai_protocol);
    if (sock == -1) {
        LOG(ERROR) << "Unable to create socket for (" << host << ":" << port
                   << "): " << strerror(errno) << "\n";
        return -1;
    }

    if (reuseaddr) {
        // set SO_REUSEADDR on the listening socket.
        int on = 1;
        int rc;
        rc = setsockopt(sock,
            SOL_SOCKET,
            SO_REUSEADDR,
            reinterpret_cast<char*>(&on),
            sizeof(on));
        if (rc < 0) {
            close(sock);
            LOG(FATAL) << "setsockopt() failed fd=" << listen_fd << "\n";
        }
    }
#ifndef SO_REUSEPORT
#define SO_REUSEPORT 15
#endif
    if (reuseport) {
        // set SO_REUSEPORT on the listening socket.
        int on = 1;
        int rc;
        rc = setsockopt(sock,
            SOL_SOCKET,
            SO_REUSEPORT,
            reinterpret_cast<char*>(&on),
            sizeof(on));
        if (rc < 0) {
            close(sock);
            LOG(FATAL) << "setsockopt() failed fd=" << listen_fd << "\n";
        }
    }

    if (bind(sock, results->ai_addr, results->ai_addrlen)) {
        // If we are waiting for the interface to be raised, such as in an
        // HA environment, ignore reporting any errors.
        int saved_errno = errno;
        if (!wait_for_iface || errno != EADDRNOTAVAIL) {
            LOG(ERROR) << "Bind was unsuccessful for (" << host << ":" << port
                       << "): " << strerror(errno) << "\n";
        }
        // if we knew that we were not multithreaded, we could do the following:
        // " : " << strerror(errno) << "\n";
        if (CloseSocket(&sock, 100)) {
            if (saved_errno == EADDRNOTAVAIL) {
                return -3;
            }
            return -2;
        } else {
            // couldn't even close the dang socket?!
            LOG(ERROR) << "Unable to close the socket.. Considering this a fatal "
                          "error, and exiting\n";
            exit(EXIT_FAILURE);
            return -1;
        }
    }

    if (disable_nagle) {
        if (!SetTCPNoDelay(sock, /*no_delay=*/true)) {
            close(sock);
            LOG(FATAL) << "SetTCPNoDelay() failed on fd: " << sock;
            return -1;
        }
    }

    if (listen(sock, backlog)) {
        // listen was unsuccessful.
        LOG(ERROR) << "Listen was unsuccessful for (" << host << ":" << port
                   << "): " << strerror(errno) << "\n";
        // if we knew that we were not multithreaded, we could do the following:
        // " : " << strerror(errno) << "\n";

        if (CloseSocket(&sock, 100)) {
            sock = -1;
            return -1;
        } else {
            // couldn't even close the dang socket?!
            LOG(FATAL) << "Unable to close the socket.. Considering this a fatal "
                          "error, and exiting\n";
        }
    }

    // If we've gotten to here, Yeay! Success!
    *listen_fd = sock;

    return 0;
}

int CreateTCPClientSocket(const std::string& host,
    const std::string& port,
    bool is_numeric_host_address,
    bool disable_nagle,
    int* connect_fd)
{
    const char* node = NULL;
    const char* service = NULL;

    *connect_fd = -1;
    if (!host.empty())
        node = host.c_str();
    if (!port.empty())
        service = port.c_str();

    struct addrinfo* results = 0;
    struct addrinfo hints;
    memset(&hints, 0, sizeof(hints));

    if (is_numeric_host_address)
        hints.ai_flags = AI_NUMERICHOST; // iff you know the name is numeric.
    hints.ai_flags |= AI_PASSIVE;

    hints.ai_family = PF_INET;
    hints.ai_socktype = SOCK_STREAM;

    int err = 0;
    if ((err = getaddrinfo(node, service, &hints, &results))) {
        // gai_strerror -is- threadsafe, so we get to use it here.
        LOG(ERROR) << "getaddrinfo for (" << node << ":" << service
                   << "): " << gai_strerror(err);
        return -1;
    }
    // this will delete the addrinfo memory when we return from this function.
    AddrinfoGuard addrinfo_guard(results);

    int sock = socket(results->ai_family, results->ai_socktype, results->ai_protocol);
    if (sock == -1) {
        LOG(ERROR) << "Unable to create socket for (" << node << ":" << service
                   << "): " << strerror(errno);
        return -1;
    }

    if (!base::SetNonBlocking(sock)) {
        LOG(FATAL) << "base::SetNonBlocking failed: " << sock;
    }

    if (disable_nagle) {
        if (!SetTCPNoDelay(sock, /*no_delay=*/true)) {
            close(sock);
            LOG(FATAL) << "SetTCPNoDelay() failed on fd: " << sock;
            return -1;
        }
    }

    int ret_val = 0;
    if (connect(sock, results->ai_addr, results->ai_addrlen)) {
        if (errno != EINPROGRESS) {
            LOG(ERROR) << "Connect was unsuccessful for (" << node << ":" << service
                       << "): " << strerror(errno);
            close(sock);
            return -1;
        }
    } else {
        ret_val = 1;
    }

    // If we've gotten to here, Yeay! Success!
    *connect_fd = sock;

    return ret_val;
}

} // namespace net
